import pandas as pd
import numpy as np
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
import matplotlib.pyplot as plt

# 设置中文字体为黑体，用来正常显示中文标签，并设置能正常显示负号
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


# 函数用于加载数据，添加异常处理，并返回数据的基本信息（行数、列数）
def load_wine_data(data_url, column_names):
    try:
        data_frame = pd.read_csv(data_url, header=None, names=column_names)
        data_shape_info = f"加载的数据形状为：{data_frame.shape[0]}行，{data_frame.shape[1]}列"
        print(data_shape_info)
        return data_frame
    except Exception as e:
        print(f"读取数据出现错误: {e}")
        return None


# 函数用于筛选指定类别标签的数据，同时返回筛选后各类别的样本数量信息
def filter_wine_data_by_class(data, target_classes):
    filtered_data = data[data['Class'].isin(target_classes)]
    feature_data = filtered_data.iloc[:, 1:]
    class_labels = filtered_data['Class']
    class_counts = filtered_data['Class'].value_counts().to_dict()
    class_count_info = "筛选后各类别样本数量："
    for class_num, count in class_counts.items():
        class_count_info += f"类别{class_num}：{count}个；"
    print(class_count_info.rstrip('；'))
    return feature_data, class_labels


# 函数用于执行PCA降维操作，除返回降维结果外，还返回降维后各主成分的累计方差贡献率
def execute_pca(feature_data, num_components=2):
    pca_model = PCA(n_components=num_components)
    pca_result = pca_model.fit_transform(feature_data)
    cumulative_variance_ratio = np.sum(pca_model.explained_variance_ratio_)
    variance_info = f"PCA降维后累计方差贡献率为：{cumulative_variance_ratio:.2%}"
    print(variance_info)
    return pca_result


# 函数用于执行LDA降维操作，返回降维结果以及解释的方差比例
def execute_lda(feature_data, class_labels, num_components=1):
    lda_model = LDA(n_components=num_components)
    lda_result = lda_model.fit_transform(feature_data, class_labels)

    # 输出LDA模型解释的方差比例，作为降维效果的一个衡量标准
    if hasattr(lda_model, 'explained_variance_ratio_'):
        variance_info = f"LDA降维后累计方差贡献率为：{np.sum(lda_model.explained_variance_ratio_):.2%}"
        print(variance_info)

    return lda_result


# 计算类间散度矩阵(S_B)和类内散度矩阵(S_W)
def compute_scatter_matrices(X, y):
    class_labels = np.unique(y)
    overall_mean = np.mean(X, axis=0)

    S_W = np.zeros((X.shape[1], X.shape[1]))
    S_B = np.zeros((X.shape[1], X.shape[1]))

    for label in class_labels:
        Xi = X[y == label]
        mean_vec = np.mean(Xi, axis=0)
        S_W += (Xi - mean_vec).T.dot((Xi - mean_vec))
        n = Xi.shape[0]
        mean_diff = mean_vec - overall_mean
        S_B += n * np.outer(mean_diff, mean_diff)

    return S_W, S_B


# 函数用于可视化PCA降维结果，添加数据点数量标注
def visualize_pca_result(pca_data, class_labels):
    plt.figure(figsize=(8, 6))
    unique_labels = np.unique(class_labels)
    for label in unique_labels:
        label_data = pca_data[class_labels == label]
        plt.scatter(label_data[:, 0], label_data[:, 1], label=f'类别{label}', s=50)
    plt.title('PCA: 降维到二维的散点图')
    plt.xlabel('主成分 1')
    plt.ylabel('主成分 2')
    plt.legend()
    plt.grid()
    data_point_count = len(pca_data)
    plt.text(0.5, 0.9, f"数据点总数：{data_point_count}", transform=plt.gca().transAxes, fontsize=12)
    plt.show()


# 函数用于可视化LDA降维结果，添加数据范围标注
def visualize_lda_result(lda_data, class_labels):
    plt.figure(figsize=(8, 3))
    unique_labels = np.unique(class_labels)
    for label in unique_labels:
        label_data = lda_data[class_labels == label]
        plt.scatter(label_data, [0] * len(label_data), label=f'类别{label}', s=50)
    plt.title('LDA: 降维到一维的散点图')
    plt.xlabel('线性判别 1')
    plt.yticks([])
    plt.legend()
    plt.grid()
    data_range = f"数据范围：[{np.min(lda_data):.2f}, {np.max(lda_data):.2f}]"
    plt.text(0.5, 0.9, data_range, transform=plt.gca().transAxes, fontsize=12)
    plt.show()


if __name__ == "__main__":
    # 数据文件的URL地址
    wine_data_url = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data"
    # 数据的列名列表
    wine_column_names = ['Class', 'Alcohol', 'Malic_acid', 'Ash', 'Alcalinity_of_ash', 'Magnesium', 'Total_phenols',
                         'Flavanoids', 'Nonflavanoid_phenols', 'Proanthocyanins', 'Color_intensity', 'Hue',
                         'OD280/OD315', 'Proline']

    # 步骤 1：加载数据
    wine_data = load_wine_data(wine_data_url, wine_column_names)

    if wine_data is not None:
        # 筛选类别1和类别2的数据
        feature_matrix, class_label_vector = filter_wine_data_by_class(wine_data, [1, 2])

        # PCA降维到二维
        pca_matrix = execute_pca(feature_matrix)
        print("PCA降维后的二维特征：")
        print(pca_matrix[:5])  # 只打印前5行数据

        # LDA降维到一维
        lda_vector = execute_lda(feature_matrix, class_label_vector)
        print("LDA降维后的一维特征：")
        print(lda_vector[:5])  # 只打印前5行数据

        # 计算类间散度与类内散度的比值
        S_W, S_B = compute_scatter_matrices(feature_matrix.values, class_label_vector.values)
        scatter_ratio = np.trace(S_B) / np.trace(S_W) if np.trace(S_W) != 0 else 0
        print(f"类间散度与类内散度比值为：{scatter_ratio:.2f}")

        # 数据可视化
        # PCA 可视化
        visualize_pca_result(pca_matrix, class_label_vector)

        # LDA 可视化
        visualize_lda_result(lda_vector, class_label_vector)